#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <string.h>
#include <time.h>
#include <rtdbg.h>
#include <stdbool.h>

#define SUNXI_RTC_PBASE 0x07090000

enum {
	RTC_LOSC_CTRL				= 0x000,
	RTC_LOSC_AUTO_SWT_STA		= 0x004,
	RTC_INTOSC_CLK_PRESCAL		= 0x008,
	RTC_RTC_DAY					= 0x010,
	RTC_RTC_HH_MM_SS			= 0x014,
	RTC_ALARM0_COUNTER			= 0x020,
	RTC_ALARM0_CUR_VLU			= 0x024,
	RTC_ALARM0_ENABLE			= 0x028,
	RTC_ALARM0_IRQ_EN			= 0x02c,
	RTC_ALARM0_IRQ_STA			= 0x030,
	RTC_ALARM_CONFIG			= 0x050,
	RTC_FANOUT_GATING			= 0x060,
	RTC_GP_DATA					= 0x100,
	RTC_FROOT_INFO_REG0			= 0x120,
	RTC_FROOT_INFO_REG1			= 0x124,
	RTC_DCXO_CTRL				= 0x160,
	RTC_RTC_VIO					= 0x190,
	RTC_IC_CHARA				= 0x1f0,
	RTC_VDDOFF_GATING_SOF		= 0x1f4,
	RTC_EFUSE_HV_PWRSWT_CTRL	= 0x204,
	RTC_SPI_CLK_CTRL_REG		= 0x310,
};

#define read32(addr)           (*(volatile unsigned int *)(addr))
#define read8(addr) 			( *(volatile unsigned char *)(addr))
#define write32(addr, value)    (*(volatile unsigned int *)(addr) = (value))
#define write8(addr, value)    (*(volatile unsigned char *)(addr) = (value))

#define TIME_OUT    200

struct rtc_t113_pdata_t {
	uint32_t virt;
	char * clk;
};

static struct rtc_t113_pdata_t _rtc_bus;

#define LEAPS_THRU_END(y)	((y) / 4 - (y) / 100 + (y) / 400)
#define LEAP_YEAR(year)		((!(year % 4) && (year % 100)) || !(year % 400))

static inline int rtc_month_days(unsigned int year, unsigned int month)
{
	const int rtc_days_in_month[13] = {
		0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
	};
	if(month > 12)
		month = 0;
	return rtc_days_in_month[month] + ((((!(year % 4) && (year % 100)) || !(year % 400)) && (month == 2)) ? 1 : 0);
}

void secs_to_rtc_time(uint64_t time, struct tm * rt)
{
	int year;
	int month;
	int days;
	int newdays;

	days = time / 86400;
	time -= (uint64_t)days * 86400;

	rt->tm_wday = (days + 4) % 7;
	year = 1970 + days / 365;
	days -= (year - 1970) * 365	+ LEAPS_THRU_END(year - 1) - LEAPS_THRU_END(1970 - 1);

	if(days < 0)
	{
		year -= 1;
		days += 365 + LEAP_YEAR(year);
	}
	rt->tm_year = year;
	rt->tm_mday = days + 1;

	for(month = 1; month < 12; month++)
	{
		newdays = days - rtc_month_days(year, month);
		if(newdays < 0)
			break;
		days = newdays;
	}
	rt->tm_mon = month;
	rt->tm_mday = days + 1;
	rt->tm_hour = time / 3600;
	time -= rt->tm_hour * 3600;
	rt->tm_min = time / 60;
	rt->tm_sec = time - rt->tm_min * 60;
}

uint64_t rtc_time_to_secs(struct tm *rt)
{
	int month = rt->tm_mon;
	int year = rt->tm_year;

	if(0 >= (int)(month -= 2))
	{
		month += 12;
		year -= 1;
	}
    // return ((((uint64_t)(year / 4 - year / 100 + year / 400 + 367 * month / 12 + rt->day) + year * 365 - 719499) * 24 + rt->hour) * 60 + rt->minute) * 60 + rt->second;
	return ((((uint64_t)(year / 4 - year / 100 + year / 400 + 367 * month / 12 + rt->tm_mday) + year * 365 - 719499) * 24 + rt->tm_hour) * 60 + rt->tm_min) * 60 + rt->tm_sec;
}


static int rtc_t113_wait(uint32_t virt, uint32_t offset, uint32_t mask, uint32_t ms)
{
	rt_tick_t timeout = rt_tick_get() + ms;
	uint32_t val;
	do {
		val = read32(virt + offset);
		val &= mask;
		if(val != mask)
			return 1;
	} while(rt_tick_get() < timeout);
	return 0;
}

static bool rtc_t113_settime(uint32_t virt, struct tm *time)
{
	uint32_t d, t;
	t = (time->tm_sec << 0) | (time->tm_min << 8) | (time->tm_hour << 16);

	d = rtc_time_to_secs(time) / (24 * 3600);

	if(!rtc_t113_wait(virt, RTC_LOSC_CTRL, 1 << 8, 50))
		return RT_FALSE;
	rt_thread_delay(2);
	write32(virt + RTC_RTC_HH_MM_SS, t);
	if(!rtc_t113_wait(virt, RTC_LOSC_CTRL, 1 << 8, 50))
		return RT_FALSE;
	rt_thread_delay(2);

	if(!rtc_t113_wait(virt, RTC_LOSC_CTRL, 1 << 7, 50))
		return RT_FALSE;
	rt_thread_delay(2);
	write32(virt + RTC_RTC_DAY, d);
	if(!rtc_t113_wait(virt, RTC_LOSC_CTRL, 1 << 7, 50))
		return RT_FALSE;
	rt_thread_delay(2);

	return RT_TRUE;
}

static bool rtc_t113_gettime(uint32_t virt, struct tm *time)
{
	uint32_t d, t;
	uint64_t tmp;

	do {
		d = read32(virt + RTC_RTC_DAY);
		t = read32(virt + RTC_RTC_HH_MM_SS);
	} while((d != read32(virt + RTC_RTC_DAY)) || (t != read32(virt + RTC_RTC_HH_MM_SS)));
	tmp = ((t >> 0) & 0x3f) + ((t >> 8) & 0x3f) * 60 + ((t >> 16) & 0x1f) * 3600 + d * 24 * 3600;
	secs_to_rtc_time(tmp, time);
	return RT_TRUE;
}

static rt_err_t rtc_init(struct rt_device *dev)
{
    uint32_t virt = SUNXI_RTC_PBASE;
    write32(virt + RTC_LOSC_CTRL, (1 << 14) | (1 << 4));
	uint32_t val = read32(virt + RTC_LOSC_CTRL);
	val &= ~(0x3 << 0);
	val |= (0x16aa << 16);
	write32(virt + RTC_LOSC_CTRL, val);
    return RT_EOK;
}

static rt_err_t rtc_control(rt_device_t dev, int cmd, void *args)
{
    rt_err_t result = RT_EOK;
    struct rtc_t113_pdata_t *rtc = dev->user_data;
    struct tm time_temp;
    struct tm *time_now;

    switch (cmd)
    {
    case RT_DEVICE_CTRL_RTC_GET_TIME:

        memset(&time_temp, 0, sizeof(time_temp));

        if (rtc_t113_gettime(rtc->virt, &time_temp) != RT_TRUE)
        {
            LOG_E("rtc gettime failed!\n");
            return -RT_ERROR;
        }
        time_temp.tm_mon = time_temp.tm_mon -1;
        time_temp.tm_year = time_temp.tm_year - 1900;
        *((time_t *)args) = mktime(&time_temp);

        time_now = localtime((const time_t *)args);
        break;
    case RT_DEVICE_CTRL_RTC_SET_TIME:

        rt_enter_critical();
        /* converts calendar time time into local time. */
        time_now = localtime((const time_t *)args);
        time_now->tm_year = time_now->tm_year + 1900;
        time_now->tm_mon += 1;
        /* copy the statically located variable */
        memcpy(&time_temp, time_now, sizeof(struct tm));
        /* unlock scheduler. */
        rt_exit_critical();

        if (rtc_t113_settime(rtc->virt, time_now) != RT_TRUE)
        {
            LOG_E("rtc settime failed!\n");
            return -RT_ERROR;
        }
        break;
    default:
        return -RT_EINVAL;
    }

    return result;
}

#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops rt_hw_rtc_ops =
{
    rtc_init,
    RT_NULL,
    RT_NULL,
    RT_NULL,
    RT_NULL,
    rtc_control
};
#endif

#ifdef BSP_USING_RTC
int rt_hw_rtc_init(void)
{
    rt_err_t ret = RT_EOK;
    static struct rt_device rtc_dev;

    rtc_dev.type = RT_Device_Class_RTC;
    rtc_dev.rx_indicate = RT_NULL;
    rtc_dev.tx_complete = RT_NULL;

#ifdef RT_USING_DEVICE_OPS
    rtc_dev.ops = &rt_hw_rtc_ops;
#else
    rtc_dev.init = rtc_init;
    rtc_dev.open = RT_NULL;
    rtc_dev.close = RT_NULL;
    rtc_dev.read = RT_NULL;
    rtc_dev.write = RT_NULL;
    rtc_dev.control = rtc_control;
#endif
    _rtc_bus.virt = SUNXI_RTC_PBASE;
    rtc_dev.user_data = &_rtc_bus;

    ret = rt_device_register(&rtc_dev, "rtc", RT_DEVICE_FLAG_RDWR);

    return ret;
}
INIT_DEVICE_EXPORT(rt_hw_rtc_init);
#endif


